.NET CORE 中间件 您所在的位置:网站首页 net core管道和中间件 .NET CORE 中间件

.NET CORE 中间件

2024-07-06 01:34| 来源: 网络整理| 查看: 265

什么是中间件

对于中间件我们其实并不陌生,在.NET CORE出现之前中间件的概念在OWIN应用程序中就已经普遍使用了。 中间件官方定义: 中间件是一种集成到应用管道中间来处理请求和响应的模块,每个中间件可以:

选择是否将请求传递到管道的下一个组件 可以在管道的下一个组件前后执行工作

ASP.NETCORE中的中间件本质上是一个请求委托 Func< RequestDelegate, RequestDelegate> middleware。 RequestDelegate本身也是一个委托,定义为 public delegate Task RequestDelegate(HttpContext Context)。 在ASP.NETCORE请求管道中,形成一条委托链。 委托链

请求管道短路:当委托不选择将请求传递到下一个委托时,称之为“短路”。

如何创建中间件

在ASP.NETCORE中,使用 IApplicationBuilder 来创建/插入中间件管道。提供了 Run和Use 两类方式。依赖组件包 Microsoft.AspNetCore.Http.Abstractions Run是一种 约定 的终端管道,即短路,不再执行下一个委托

public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.Run(async context => { await context.Response.WriteAsync("hello world 1"); }); //这里不会执行到!! app.Run(async context => { await context.Response.WriteAsync("hello world 2"); }); }

Use通常以扩展方法提供中间件,很适合处理一些AOP的事务。

public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.Use(async (context, next) => { //可以在invoke之前做一些事 await next.Invoke(); //可以在invoke之后做一些事 }); app.Run(async context => { await context.Response.WriteAsync("hello world"); }); }

实际开发中我们通常需要自己定义中间件,有两种方式可以实现。

约定方式 public class RequestIdInRequestMiddleware { private readonly RequestDelegate _next; public RequestIdInRequestMiddleware(RequestDelegate next) { _next = next; } public Task Invoke(HttpContext httpContext,IService service) { service.SayHello(); //request head 加入requestid var requestId = Guid.NewGuid().ToString("n"); httpContext.Request.Headers.Add("REQUESTID", requestId); return _next(httpContext); } }

如上有以下约定:

具有类型为 RequestDelegate 的参数公共构造函数 名为 Invoke 或 InvokeAsync 的公共方法,且此方法必须: 返回 Task 第一个参数为 HttpContext

目前官方是推荐使用约定方式, 注意:该方式加入管道中的生命周期为单例。也因此如果依赖一些Service,建议从Invoke 或 InvokeAsync的方法参数注入,而不是从构造函数注入。(可以想想为什么?单例构造函数注入对Service的生命周期有要求~~)。

强类型

官方也提供了IMiddleware接口,用于扩展创建中间件。这种方式有两个优点:

可以按需(生命周期)注入

中间件强类型话,更易理解

public class RequestIdInResponseMiddleware:IMiddleware { private readonly IService _service; public RequestIdInResponseMiddleware(IService service) { _service = service; } public Task InvokeAsync(HttpContext context, RequestDelegate next) { var requestId = Guid.NewGuid().ToString("n"); context.Response.Headers.Add("REQUESTID", requestId); return next(context); } } 中间件加入管道

中间件一般都是基于IApplicationBuilder扩展方法加入管道。

public static class RequestIdMiddlewareExtensions { public static IApplicationBuilder UseRequestIdInResponseMiddleware(this IApplicationBuilder builder) { return builder.UseMiddleware(); } }

可以在 Configure 方法中调用加入 app.UseRequestIdInResponseMiddleware(); 如果是 强类型 方式创建的Middleware,还需要在 ConfigureServices 中注册 services.AddSingleton();

中间件的顺序

中间件显著受加入的顺序影响,官方提供的默认中间件顺序图 中间件顺序

中间件分支Map

Map 扩展用来约定创建管道分支,和管道短路类似,不过它是基于给定的请求路径匹配项来创建请求管道分支。官方提供的例子,

public class Startup { private static void HandleMapTest1(IApplicationBuilder app) { app.Run(async context => { await context.Response.WriteAsync("Map Test 1"); }); } private static void HandleMapTest2(IApplicationBuilder app) { app.Run(async context => { await context.Response.WriteAsync("Map Test 2"); }); } public void Configure(IApplicationBuilder app) { app.Map("/map1", HandleMapTest1); app.Map("/map2", HandleMapTest2); app.Run(async context => { await context.Response.WriteAsync("Hello from non-Map delegate.

"); }); } }

根据请求会响应不同结果

请求 响应 localhost:1234 Hello from non-Map delegate. localhost:1234/map1 Map Test 1 localhost:1234/map2 Map Test 2 localhost:1234/map3 Hello from non-Map delegate.

另外还可以使用 UseWhen 创建管道分支,只有匹配一定条件才会短路管道。

public void Configure(IApplicationBuilder app) { //只有请求url包含查询字符串变量 branch,才会短路管道 app.UseWhen(context => context.Request.Query.ContainsKey("branch"), builder => builder.Use(async (context, next) => { var branchVer = context.Request.Query["branch"]; // Do work that doesn't write to the Response. await next(); // Do other work that doesn't write to the Response. })); app.Run(async context => { await context.Response.WriteAsync("Hello from main pipeline."); }); } 中间件的单元测试

针对中间件的单元测试,可以使用 TestServer 来进行。它有以下几个优点:

请求会发送到内存中,而不是通过网络进行序列化 避免产生额外的问题,例如端口号或Https等 中间件中的异常可以直接流回调用测试 可以直接在测试中自定义服务器数据结构,如 HttpContext

http请求发送模拟可以使用 HttpClient 和 HttpContext ,分别可以验证Response和Request Context相关功能。下面分别测试RequestIdInRequestMiddleware,RequestIdInResponseMiddleware。 新建xunit单元测试项目,加入依赖包: Microsoft.AspNetCore.TestHost , Microsoft.Extensions.Hosting 。 测试代码如下:

public class MiddlewareTest { /// /// HttpContext模拟,验证request header是否成功加入requestId /// [Fact] public void MiddlewareTest_RequestHeaderExistRequestId() { var hostBuilder = new HostBuilder() .ConfigureWebHost(webBuilder => { webBuilder .UseTestServer() .ConfigureServices((context, services) => { services.AddTransient(); }) .Configure(app => { app.UseRequestIdInRequestMiddleware(); }); }); using (var host = hostBuilder.Start()) { var context = host.GetTestServer().SendAsync(c => { c.Request.Path = "/map"; c.Request.Method = HttpMethods.Get; }).Result; Assert.True(context.Request.Headers.ContainsKey("REQUESTID")); } } /// /// HttpClient模拟,验证response header是否成功加入requestId /// [Fact] public void MiddlewareTest_ResponseHeaderExistRequestId() { var hostBuilder = new HostBuilder() .ConfigureWebHost(webBuilder => { webBuilder .UseTestServer() .ConfigureServices((context, services) => { services.AddSingleton(); services.AddTransient(); }) .Configure(app => { app.UseRequestIdInResponseMiddleware(); }); }); using (var host = hostBuilder.Start()) { host.GetTestServer().CreateRequest("/map").GetAsync() .ContinueWith(task => { var response = task.Result; Assert.True(response.Headers.Contains("REQUESTID")); }).Wait(); } } }


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有